#include "myrpcheader.pb.h"
#include "myrpcchannel.h"
#include "zookeeperutil.h"
#include "myrpcapplication.h"
#include "myrpccontroller.h"
#include "myrpclogger.h"
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <memory>
#include <mutex>

std::mutex g_data_mutx;

MyRPCChannel::MyRPCChannel(bool connectNow)
:m_clientfd(-1)
,m_idx(0)
{
    if (!connectNow) {
        return;
    }
    auto rt = newConnect(m_ip.c_str(), m_port);
    int count = 3; // 重试次数
    while (!rt && count--) {
        rt = newConnect(m_ip.c_str(), m_port);
    }
}

void MyRPCChannel::CallMethod(const google::protobuf::MethodDescriptor* method,
                          google::protobuf::RpcController* controller, 
                          const google::protobuf::Message* request,
                          google::protobuf::Message* response, 
                          google::protobuf::Closure* done)
{
    if (m_clientfd == -1) {
        // 说明此时还没有与方法所在的服务器建立连接
        // 获取服务对象指针
        const google::protobuf::ServiceDescriptor* sd = method->service();
        // 获取服务名和方法名，赋值给service_name和method_name成员

        // 写错的地方：之前在这里新定义了这两个name变量，后面序列化的时候
        // 出了if作用域，使用的是成员变量，自然获取不到数据
        service_name = sd->name();
        method_name = method->name();

        ZKClient zkclient;
        // 客户端这里与zookeeper连接是为了去zookeeper上查询
        // 对应的服务名和方法名所在的机器的ip和port
        zkclient.Start();
        // 获取服务和方法所在机器的ip和port
        std::string host_data = QueryServiceHost(&zkclient,service_name,method_name,m_idx);
        // 获取ip地址
        m_ip = host_data.substr(0,m_idx);
        std::cout << "ip: " << m_ip << std::endl;
        // 获取端口
        m_port = atoi(host_data.substr(m_idx + 1, host_data.size() - m_idx).c_str());
        std::cout << "port: " << m_port << std::endl;

        // 向对应机器的ip和port发起tcp连接
        auto ok = newConnect(m_ip.c_str(), m_port);
        if (!ok) {
            LOG(ERROR) << "connect server failed";
            return;
        } else {
            LOG(INFO) << "connect server success";
        }
    }
    // 传递的实际参数的字节数
    uint32_t args_size{};
    std::string args_str;
    if (request->SerializeToString(&args_str)) {
        // 将request中保存的函数参数数据序列化为字符串
        // 计算出参数的长度
        args_size = args_str.size();
    } else {
        controller->SetFailed("serialize request failed");
        return;
    }

    // 设置rpc头部
    myrpc::RpcHeader header;
    header.set_service_name(service_name);
    header.set_method_name(method_name);
    header.set_args_size(args_size);

    uint32_t header_size = 0;
    std::string rpc_header_str;
    if (header.SerializeToString(&rpc_header_str)) {
        // 将头部数据序列化为字符串
        header_size = rpc_header_str.size();
    } else {
        controller->SetFailed("serialize header failed");
        return;
    }

    // 组织要发送的数据
    std::string send_rpc_str;
    {
        // 创建StringOutputStream对象，将send_str作为输出流缓冲区
        google::protobuf::io::StringOutputStream string_output(&send_rpc_str);
        // 创建CodedOutputStream对象，对写入的数据进行编码后写到string_output中
        google::protobuf::io::CodedOutputStream code_output(&string_output);
        // 将header_size变成编码后写到流中
        code_output.WriteVarint32(static_cast<uint32_t>(header_size));
        // 然后将rpc头部信息以字符串形式写到流中
        code_output.WriteString(rpc_header_str);

        // 流中的数据最终写到send_str中
    }
    // 追加上函数参数
    send_rpc_str += args_str;
    // 到这里send_str就是要发送的数据了
    
    // 调用socket中的send函数发送数据
    if (-1 == send(m_clientfd, send_rpc_str.c_str(), send_rpc_str.size(), 0)) {
        close(m_clientfd);
        char errtxt[512] = {0};
        std::cout << "send error  " << strerror_r(errno,errtxt, sizeof(errtxt)) << "\n";
        controller->SetFailed(errtxt);
        return;
    }

    // 接收响应的数据
    char recv_buf[1024] = {0};
    int recv_size = 0;
    // recv_size表示从远端读取的实际字节数
    if (-1 == (recv_size = recv(m_clientfd,recv_buf,1024, 0))) {
        char errtxt[512] = {0};
        std::cout << "recv error" << strerror_r(errno, errtxt, sizeof(errtxt)) << "\n";
        controller->SetFailed(errtxt);
        return;
    }
    if (!response->ParseFromArray(recv_buf,recv_size)) {
        // 从接收的recv_buf中将数据解析 到response中
        close(m_clientfd);
        char errtxt[512] = {0};
        std::cout << "parse error" << strerror_r(errno, errtxt,sizeof(errtxt)) << "\n";
        controller->SetFailed(errtxt);
        return;
    }
    // 关闭文件描述符
    close(m_clientfd);
}

bool MyRPCChannel::newConnect(const char* ip, uint16_t port) {
    // 通过socket网络编程调用connect连接对应的结点
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1) {
        char errtxt[512] = {0};
        //strerror_r函数是获取错误信息并将该信息写到errtxt
        std::cout << "socket error " << strerror_r(errno, errtxt, sizeof(errtxt)) << std::endl;
        LOG(ERROR) << "socket error: " << errtxt;
        return false;
    }
    // 定义socket addr结构体
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(ip);
    server_addr.sin_port = htons(port);
    if (-1 == connect(clientfd, (struct sockaddr*)&server_addr, sizeof(server_addr))) {
        // connect函数发起连接
        close(clientfd);
        char errtxt[512] = {0};
        std::cout << "connect error" << strerror_r(errno, errtxt, sizeof(errtxt)) << "\n";
        LOG(ERROR) << "connect error:" << errtxt;
        return false;
    }
    m_clientfd = clientfd;
    return true;
}

std::string MyRPCChannel::QueryServiceHost(ZKClient* zkclient, 
                                           std::string service_name, 
                                           std::string method_name, 
                                           int& idx)
{
    // 首先组织方法路径
    std::string method_path = "/" + service_name + "/" + method_name;
    std::cout << "method_path: " << method_path << std::endl;
    std::unique_lock<std::mutex> lock(g_data_mutx); // 先加锁
    std::string host_data_1 = zkclient->GetData(method_path.c_str());
    lock.unlock(); // 解锁
    if (host_data_1 == "") {
        // 说明对应的方法不存在
        LOG(ERROR) << method_path + " is not exist!";
        return " ";
    }
    // 找到':'所在的下标，即ip和port的分隔符
    idx = host_data_1.find(":");
    if (idx == -1) {
        LOG(ERROR) << method_path + " address is invalid!";
        return " ";
    }
    // 返回ip+port的字符串形式
    return host_data_1;
}


